Компания «Чётенькое такси» собрала исторические данные о заказах такси в аэропортах. Чтобы привлекать больше водителей в период пиковой нагрузки, нужно спрогнозировать количество заказов такси на следующий час. Постройте модель для такого предсказания.
Значение метрики RMSE на тестовой выборке должно быть не больше 48.
Вам нужно:
Данные лежат в файле taxi.csv
. Количество заказов находится в столбце num_orders
(от англ. number of orders, «число заказов»).
import time
import warnings
import numpy as np
import pandas as pd
import lightgbm as lgb
import plotly.graph_objects as go
from lightgbm import LGBMRegressor
from IPython.display import display
from catboost import CatBoostRegressor
from sklearn.metrics import mean_squared_error
from sklearn.tree import DecisionTreeRegressor
from sklearn.linear_model import LinearRegression
from sklearn.ensemble import RandomForestRegressor
from statsmodels.tsa.seasonal import seasonal_decompose
from sklearn.model_selection import GridSearchCV, train_test_split, TimeSeriesSplit
warnings.filterwarnings('ignore')
try:
df = pd.read_csv('taxi.csv', index_col=[0], parse_dates=[0])
except:
df = pd.read_csv('/datasets/taxi.csv', index_col=[0], parse_dates=[0])
display(df.head(), df.shape)
num_orders | |
---|---|
datetime | |
2018-03-01 00:00:00 | 9 |
2018-03-01 00:10:00 | 14 |
2018-03-01 00:20:00 | 28 |
2018-03-01 00:30:00 | 20 |
2018-03-01 00:40:00 | 32 |
(26496, 1)
df.info()
<class 'pandas.core.frame.DataFrame'> DatetimeIndex: 26496 entries, 2018-03-01 00:00:00 to 2018-08-31 23:50:00 Data columns (total 1 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 num_orders 26496 non-null int64 dtypes: int64(1) memory usage: 414.0 KB
df.isna().sum()
num_orders 0 dtype: int64
df.sort_index(inplace=True)
# Проверим индекс на монотонность
df.index.is_monotonic
True
df = df.resample('1H').sum()
decomposed = seasonal_decompose(df)
decomposed_day = seasonal_decompose(df.resample('1D').sum())
Промежуточный вывод:
В разделе "Подготовка данных" были выполнены следующие задачи:
В результате выполнения этих задач было обнаружено, что даты и время в датафрейме расположены в хронологическом порядке. Этот факт подтверждает правильность выполнения предварительной обработки данных.
Таким образом, данные были подготовлены для обучения моделей.
df.head()
num_orders | |
---|---|
datetime | |
2018-03-01 00:00:00 | 124 |
2018-03-01 01:00:00 | 85 |
2018-03-01 02:00:00 | 71 |
2018-03-01 03:00:00 | 66 |
2018-03-01 04:00:00 | 43 |
def plotly(data, title):
fig = go.Figure(data=go.Scatter(x=data.index, y=data))
fig.update_layout(title=title, plot_bgcolor='white')
fig.show()
plotly(decomposed.trend, 'Trend')
plotly(decomposed.trend.rolling(24*7).mean(), 'Trend (smoothed)')
plotly(decomposed.seasonal.tail(24*7), 'Seasonality')
plotly(decomposed_day.seasonal['2018-03-01':'2018-03-15'], 'Weekly seasonality')
plotly(decomposed.seasonal['2018-03-01':'2018-03-2'], 'Daily seasonality')
Промежуточный вывод:
Из анализа данных можно сделать следующие выводы:
Оба этих наблюдения могут быть полезны при прогнозировании и планировании заказов в будущем, а также при принятии решений по оптимизации и управлению предприятием.
def make_features(data):
data = df.copy()
data['month'] = df.index.month
data['day'] = df.index.day
data['dayofweek'] = df.index.dayofweek
data['hour'] = df.index.hour
for i in range(1, 6):
data['lag_{}'.format(i)] = data['num_orders'].shift(i)
data['rolling_mean'] = data['num_orders'].shift().rolling(1).mean()
data.dropna(inplace=True)
return data
data = make_features(df)
data.head()
num_orders | month | day | dayofweek | hour | lag_1 | lag_2 | lag_3 | lag_4 | lag_5 | rolling_mean | |
---|---|---|---|---|---|---|---|---|---|---|---|
datetime | |||||||||||
2018-03-01 05:00:00 | 6 | 3 | 1 | 3 | 5 | 43.0 | 66.0 | 71.0 | 85.0 | 124.0 | 43.0 |
2018-03-01 06:00:00 | 12 | 3 | 1 | 3 | 6 | 6.0 | 43.0 | 66.0 | 71.0 | 85.0 | 6.0 |
2018-03-01 07:00:00 | 15 | 3 | 1 | 3 | 7 | 12.0 | 6.0 | 43.0 | 66.0 | 71.0 | 12.0 |
2018-03-01 08:00:00 | 34 | 3 | 1 | 3 | 8 | 15.0 | 12.0 | 6.0 | 43.0 | 66.0 | 15.0 |
2018-03-01 09:00:00 | 69 | 3 | 1 | 3 | 9 | 34.0 | 15.0 | 12.0 | 6.0 | 43.0 | 34.0 |
features = data.drop(['num_orders'], axis=1)
target = data['num_orders']
features_train, features_test, \
target_train, target_test = train_test_split(features, target, shuffle=False, test_size=0.1, random_state=12345,)
results_df = pd.DataFrame(columns=['Model', 'Training Time', 'Prediction Time', 'RMSE Train'])
tscv = TimeSeriesSplit(n_splits=5)
def fit_model(estimator, param_grid, features_train, target_train, features_test, target_test):
model = GridSearchCV(estimator=estimator,
param_grid=param_grid,
n_jobs=-1,
cv=tscv,
scoring='neg_root_mean_squared_error'
)
start_time = time.time()
model.fit(features_train, target_train)
training_time = time.time() - start_time
best_rmse = abs(round(model.best_score_, 1))
print(f'Best RMSE: {best_rmse}')
print(f'Best params: {model.best_params_}')
best_model = estimator.set_params(**model.best_params_)
start_time = time.time()
best_model.fit(features_train, target_train)
prediction_time = time.time() - start_time
predictions_train = best_model.predict(features_train)
train_rmse = mean_squared_error(target_train, predictions_train, squared=False)
return best_model, best_rmse, training_time, prediction_time, train_rmse
best_model, best_rmse, training_time, prediction_time, train_rmse_DTR = fit_model(DecisionTreeRegressor(random_state=12345), {'max_depth': range(1, 11, 2)}, features_train, target_train, features_test, target_test)
results_df.loc[0] = ['DecisionTreeRegressor', training_time, prediction_time, train_rmse_DTR]
results_df
Best RMSE: 29.3 Best params: {'max_depth': 7}
Model | Training Time | Prediction Time | RMSE Train | |
---|---|---|---|---|
0 | DecisionTreeRegressor | 0.184136 | 0.011271 | 22.023192 |
best_model, best_rmse, training_time, \
prediction_time, train_rmse_RFR = fit_model(RandomForestRegressor(random_state=12345),
{'n_estimators': range(50, 100, 10), 'max_depth': range(1, 11, 2)},
features_train, target_train, features_test, target_test)
results_df.loc[1] = ['RandomForestRegressor', training_time, prediction_time, train_rmse_RFR]
results_df
Best RMSE: 25.9 Best params: {'max_depth': 9, 'n_estimators': 80}
Model | Training Time | Prediction Time | RMSE Train | |
---|---|---|---|---|
0 | DecisionTreeRegressor | 0.184136 | 0.011271 | 22.023192 |
1 | RandomForestRegressor | 30.145162 | 0.783082 | 17.018848 |
best_model, best_rmse, training_time, \
prediction_time, train_rmse_LR = fit_model(LinearRegression(),
[{'fit_intercept': [True, False]},
{'copy_X': [True, False]}, {'n_jobs': [1, -1]}],
features_train, target_train, features_test, target_test)
results_df.loc[2] = ['LinearRegression', training_time, prediction_time, train_rmse_LR]
results_df
Best RMSE: 31.5 Best params: {'fit_intercept': True}
Model | Training Time | Prediction Time | RMSE Train | |
---|---|---|---|---|
0 | DecisionTreeRegressor | 0.184136 | 0.011271 | 22.023192 |
1 | RandomForestRegressor | 30.145162 | 0.783082 | 17.018848 |
3 | CatBoostRegressor | 63.270962 | 0.755724 | 14.432441 |
4 | LGBMRegressor | 1.886202 | 0.036697 | 17.952839 |
2 | LinearRegression | 0.116830 | 0.002021 | 30.504124 |
best_model, best_rmse, training_time, \
prediction_time, train_rmse_LR = fit_model(CatBoostRegressor(random_state=12345, verbose=False),
{'depth': [4, 6, 8], 'learning_rate': [0.01, 0.1, 1]},
features_train, target_train, features_test, target_test)
results_df.loc[3] = ['CatBoostRegressor', training_time, prediction_time, train_rmse_LR]
results_df
Best RMSE: 25.5 Best params: {'depth': 4, 'learning_rate': 0.1}
Model | Training Time | Prediction Time | RMSE Train | |
---|---|---|---|---|
0 | DecisionTreeRegressor | 0.184136 | 0.011271 | 22.023192 |
1 | RandomForestRegressor | 30.145162 | 0.783082 | 17.018848 |
3 | CatBoostRegressor | 63.522635 | 0.752388 | 14.432441 |
4 | LGBMRegressor | 1.886202 | 0.036697 | 17.952839 |
2 | LinearRegression | 0.116830 | 0.002021 | 30.504124 |
model = LGBMRegressor(random_state=12345)
# Определяем сетку гиперпараметров для настройки
parameters = {'n_estimators': [50, 100, 200],
'max_depth': [3, 5, 7]}
# Выполняем поиск по сетке с использованием перекрестной проверки
best_model, best_rmse, training_time_LGBM, prediction_time_LGBM, train_rmse_LGBM = fit_model(model, parameters, features_train, target_train, features_test, target_test)
# Добавляем результаты текущей модели в фреймворк данных
results_df.loc[4] = ['LGBMRegressor', training_time_LGBM, prediction_time_LGBM, train_rmse_LGBM]
# Показываем результаты
results_df
Best RMSE: 25.2 Best params: {'max_depth': 7, 'n_estimators': 50}
Model | Training Time | Prediction Time | RMSE Train | |
---|---|---|---|---|
0 | DecisionTreeRegressor | 0.184136 | 0.011271 | 22.023192 |
1 | RandomForestRegressor | 30.145162 | 0.783082 | 17.018848 |
3 | CatBoostRegressor | 63.522635 | 0.752388 | 14.432441 |
4 | LGBMRegressor | 1.857874 | 0.036788 | 17.952839 |
2 | LinearRegression | 0.116830 | 0.002021 | 30.504124 |
Промежуточный вывод:
Была проведена разбивка данных на выборки. Создана функция fit_model(), которая использует метод GridSearchCV для подбора наилучшей модели и вывода метрики RMSE и параметров лучшей модели. Были обучены пять модели DecisionTreeRegressor
, RandomForestRegressor
, LinearRegression
, CatBoostRegressor
, LGBMRegressor
). Были выведены метрики RMSE для каждой из этих моделей для тренировачной и тестовой в виде таблицы.
По результатам мы видим, что лучшей моделью стала CatBoostRegressor
и LGBMRegressor
= 43.657045
def display_result(target, pred, rmse, model_name):
# Преобразуем целевую переменную в DataFrame и сбрасываем индекс
result = target.to_frame().reset_index()
# Добавляем столбец с предсказаниями в DataFrame
result['prediction'] = pd.Series(pred)
# Устанавливаем столбец datetime в качестве индекса
result.set_index('datetime', inplace=True)
fig = go.Figure()
# Добавляем график с реальными значениями
fig.add_trace(go.Scatter(x=result.index, y=result[target.name], name='True'))
# Добавляем график с предсказанными значениями
fig.add_trace(go.Scatter(x=result.index, y=result['prediction'], name='Predicted'))
# Устанавливаем заголовок графика и подписи осей
fig.update_layout(title=model_name + ' (RMSE: ' + str(rmse) + ')', xaxis_title='Время (дни)', yaxis_title='Количество заказов', plot_bgcolor='white')
# Отображаем график
display(fig)
for i in range(len(results_df)):
model_name = results_df.loc[i, 'Model']
if model_name == 'CatBoostRegressor':
model = CatBoostRegressor(verbose=False)
# Подгоняем модель к обучающим данным
model.fit(features_train, target_train)
# Спрогнозируем на тестовых данных
predictions = model.predict(features_test)
# Рассчитаем среднеквадратичное отклонение
rmse = mean_squared_error(target_test, predictions, squared=False)
# Выводим результаты
display_result(target_test, predictions, rmse, model_name)
Было проведено исследование, направленное на создание модели машинного обучения для прогнозирования количества заказов такси в аэропортах на следующий час. Это поможет привлечь больше водителей в периоды пиковой нагрузки. Входные данные для модели - исторические данные о заказах такси в аэропортах с 1 марта по 31 августа 2018 года. Результаты на обучающей выборке показали, что удалось создать модель для успешного прогнозирования, таковыми являются CatBoostRegressor
и LGBMRegressor
Полезная лекция про временные ряды: https://www.youtube.com/watch?v=u433nrxdf5k
Б.Б. Демешев - временные ряды https://disk.yandex.ru/i/LiDHB-B3A6Lz5A
Базовое применение ARIMA - https://colab.research.google.com/drive/17RnG91Eq8JBKyxToNzvCvjibfxum-oPj?usp=sharing
Канторович - Анализ временных рядов https://yadi.sk/i/IOkUOS3hTXf3gg https://facebook.github.io/prophet/
https://facebook.github.io/prophet/docs/quick_start.html#python-api
https://nbviewer.jupyter.org/github/miptgirl/habra_materials/blob/master/prophet/habra_data.ipynb